/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: CDSContainer.java,v $
 *
 */

package com.cidero.upnp;

import java.util.ArrayList;
import java.util.Vector;

import org.w3c.dom.DOMException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *  Derived class of CDSObject used to contain other objects.
 *  
 */
public class CDSContainer extends CDSObject
{
  int            childCount = 0;    
  CDSObjectList  childList = new CDSObjectList();

  Vector     createClasses;   // (Opt.)
  Vector     searchClasses;   // (Opt.)
  boolean    searchable;      // (Opt.) 
  
  static String upnpClass = "object.container";
  static String xmlElementName = "container";

  public CDSContainer()
  {
    createClasses = new Vector();  
    searchClasses = new Vector();
    
    // Default is not searchable if not specified. Seems odd, but that's
    // what the spec says...
    searchable = false;      
  }

  public CDSContainer( Node node )
  {
    super( node );

    createClasses = new Vector();  
    searchClasses = new Vector();
    
    // Default is not searchable if not specified (UPnP spec)
    searchable = false;      

    NamedNodeMap attributes = node.getAttributes();

    String tmpString;
    
    try
    {
      Node tmpNode = attributes.getNamedItem("childCount");
      if( tmpNode != null )
      {
        tmpString = tmpNode.getNodeValue();
        childCount = Integer.parseInt( tmpString );
      }

      tmpNode = attributes.getNamedItem("searchable");
      if( tmpNode != null )
      {
        tmpString = tmpNode.getNodeValue();
        searchable = toBoolean( tmpString );
      }

    }
    catch( DOMException e )
    {
      System.out.println( "Error accessing node value: " + e.getMessage() );
    }

    // TODO: Add support for search/create class elements 
    NodeList children = node.getChildNodes();

    for( int n = 0 ; n < children.getLength() ; n++ )
    {
      String nodeName = children.item(n).getNodeName();
      
      //      if( nodeName.equals("upnp:searchClass") )
      //        artist = getSingleTextNodeValue( children.item(n) );
      //      else if( nodeName.equals("upnp:createClass") )
      //        genre = getSingleTextNodeValue( children.item(n) );

    }

  }

  public Object clone() 
  {
    // invoke base class version first
    CDSContainer obj = (CDSContainer)super.clone();

    // Do deep copy of child list
    obj.childList = (CDSObjectList)childList.clone();

    return obj;
  }


  /**
   *  Add a child object to the container. Child objects can be sub-containers
   *  or media items
   *
   *  @param  childObj   Media Item
   */
  public void addChild( CDSObject childObj )
  {
    childList.add( childObj );
    childCount = childList.size();
    childObj.setParentId( getId() );
  }

  public void insertChildAt( CDSObject childObj, int index )
  {
    childList.insertElementAt( childObj, index );
    childCount = childList.size();
    childObj.setParentId( getId() );
  }


  /**
   *  Add a child object to the container. Child objects can be sub-containers
   *  or media items
   *
   *  @param  childObj   Media Item
   */
  public void setChildCount( int childCount )
  {
    this.childCount = childCount;
  }
  public int getChildCount()
  {
    return childCount;
  }
  public CDSObject getChild( int index )
  {
    return childList.getObject( index );
  }

  public void incrChildCount()
  {
    childCount++;
  }

  /**
   *  Clear item list
   */
  public void clear()
  {
    childList.clear();
  }
  
  public CDSObjectList getChildList() {
    return childList;
  }
    
  /**
   * Convenience method to determine whether a container has 
   * children that are items (not sub-containers)
   */
  public boolean hasChildItems() 
  {
    for( int n = 0 ; n < childList.size(); n++ )
    {
      CDSObject childObj = (CDSObject)childList.get(n);
      if( childObj.isItem() )
        return true;
    }

    return false;
  }

  /**
   * Convenience method to determine whether a container has 
   * actual child nodes (not just a childCount set to non-zero. This
   * is useful to detect certain situations when the actual child nodes
   * are created only on-demand (from playlist resources on the Web most
   * typically)
   */ 
  public boolean hasChildObjects() 
  {
    if( childList.size() > 0 )
      return true;

    return false;
  }

  /**
   * In some cases, it is useful to associate an object of another type
   * with the current object if it contains useful metadata not supported
   * by the current type. This extra object is never exported via UPnP -
   * it's just used by applications 
   */
  CDSObject extraMetadataObj = null;

  public void setExtraMetadataObj( CDSObject obj )
  {
    extraMetadataObj = obj;
  }
  public CDSObject getExtraMetadataObj()
  {
    return extraMetadataObj;
  }


  /**
   *  Set searchable flag
   *
   *  @param  searchable   Searchable flag. Defaults to false if not set
   */
  public void setSearchable( boolean searchable )
  {
    this.searchable = searchable;
  }

  /**
   *  Get searchable flag
   *
   *  @return  searchable flag
   */
  public boolean getSearchable() { return searchable; }


  /**
   *  Add a create class to the container
   *
   *  @param  refId    Reference id string. Set to "-1" for non-reference items
   */
  public void addCreateClass( String name, boolean includeDerived,
                              String friendlyName )
  {
    ClassPermission createClass = new ClassPermission( name, includeDerived,
                                                       friendlyName );
    
    createClasses.add( createClass );
  }
  public void clearCreateClasses()
  {
    createClasses.clear();
  }

  /**
   *  Add a search class to the container
   *
   *  @param  refId    Reference id string. Set to "-1" for non-reference items
   */
  public void addSearchClass( String name, boolean includeDerived,
                              String friendlyName )
  {
    ClassPermission searchClass = new ClassPermission( name, includeDerived,
                                                       friendlyName );
    
    searchClasses.add( searchClass );
  }
  public void clearSearchClasses()
  {
    searchClasses.clear();
  }

  
  /**
   *  Inner Class for container's 'searchClass' and 'createClass' properties
   */
  class ClassPermission
  {
    String  name;             // Class name
    boolean includeDerived;   // Allow searches on descendants of specified 
                              // class. Required attribute (no default)
    String  friendlyName;     // Friendly name for presentation
  
    public ClassPermission( String name, boolean includeDerived, 
                            String friendlyName )
    {
      this.name = name;
      this.includeDerived = includeDerived;
      this.friendlyName = friendlyName;
    }

    public void setName( String name ) { this.name = name; }
    public String getName() { return name; }

    public void setIncludeDerived( boolean includeDerived )
    {
      this.includeDerived = includeDerived;
    }
    public boolean getIncludeDerived() { return includeDerived; }

    public void setFriendlyName( String friendlyName )
    {
      this.friendlyName = friendlyName;
    }
    public String getFriendlyName() { return name; }
  }
  
  
  /**
   *  Get object class
   *
   *  @return  UPNP class string
   */
  public String getUPNPClass() { return upnpClass; }
  public String getObjectXMLElementName() { return xmlElementName; }

  public String attributesToXML( CDSFilter filter )
  {
    StringBuffer buf = new StringBuffer();

    buf.append( super.attributesToXML( filter ) );

    // Optional properties
    //if( filter.propertyEnabled("container@childCount") )
    if( filter.propertyEnabled("@childCount") )
      buf.append(" childCount=\"" + childCount + "\"" );
    if( filter.propertyEnabled("@searchable") )
      buf.append(" searchable=\"" + searchable + "\"" );

    return buf.toString();
  }

  public String elementsToXML( CDSFilter filter )
  {
    // Need to build element string starting with elements in base classes
    StringBuffer buf = new StringBuffer();

    buf.append( super.elementsToXML( filter ) );
    
    // Add all create class and search class elements

    ClassPermission classPerm;

    if( filter.propertyEnabled("upnp:createClass") )
    {
      for( int n = 0 ; n < createClasses.size() ; n++ )
      {
        classPerm = (ClassPermission)createClasses.get(n);
        buf.append( "  <upnp:createClass includeDerived=\"" + 
                    classPerm.getIncludeDerived() + "\">\n    " +
                    classPerm.getName() + "\n  </upnp:createClass>\n" );
      }
    }
    
    if( filter.propertyEnabled("upnp:searchClass") )
    {
      for( int n = 0 ; n < searchClasses.size() ; n++ )
      {
        classPerm = (ClassPermission)searchClasses.get(n);
        buf.append( "  <upnp:searchClass includeDerived=\"" + 
                    classPerm.getIncludeDerived() + "\">\n      " +
                    classPerm.getName() + "\n  </upnp:searchClass>\n" );
      }
    }
    
    return buf.toString();
  }

  public boolean isContainer() { return true; }


  /**
   *  Sort the container's object list by the title property
   */
  public void sortByTitle()
  {
    childList.sortByTitle();
  }

  /**
   * Search container for object with the specified title. For now, the
   * search just quits on the 1st match and returns a single object. 
   * When a proper search infrastructure is developed, this will change
   * (TODO - CDS object search methods)
   */ 
  public CDSObject searchByTitle( String searchString )
  {
    for( int n = 0 ; n < childList.size(); n++ )
    {
      CDSObject childObj = (CDSObject)childList.get(n);
      String title = childObj.getTitle();
      //System.out.println("Checking title " + title + " = " + searchString );
      
      if( (title != null) && (title.indexOf( searchString ) >= 0) )
        return childObj;
    }
    return null;
  }


  public static void main( String[] args )
  {
    CDSContainer obj = new CDSContainer();

    obj.setId("0");
    obj.setParentId("-1");
    obj.setTitle("Testing");
    obj.setCreator("Me");
    obj.setRestricted( false );
    obj.setWriteStatus( "WRITABLE" );
    
    obj.addCreateClass( "object.container.storageFolder", false, null );

    CDSFilter filter = new CDSFilter("*");

    System.out.println( obj.toXML( filter ) );
    
  }
  

}
